using System;
using System.Data;
using FireStarter.Base.BusinessObjectMerger;
using Firestarter.DB.Schema.BO;
using Habanero.Base;
using Habanero.DB;

namespace Firestarter.DB.Schema.Logic
{
    public class SchemaMerger
    {
        public SchemaMerger(IReverseEngineering reverseEngineering)
        {
            ReverseEngineering = reverseEngineering;
        }

        public IReverseEngineering ReverseEngineering { get; private set; }

        public virtual void RunMerge()
        {
            DBSchemaExtractor schemaExtractor;
            IDbConnection connection = CreateOpenConnection();
            try
            {
                string vendor = ReverseEngineering.DatabaseConnectionInfo.Vendor.ToUpper();
                //DBSchemaExtractor schemaExtractor = new DBSchemaExtractorMySQL(connection);
                schemaExtractor = GetDBSchemaExtractor(vendor, connection);

                var originalExceptionNotifier = GlobalRegistry.UIExceptionNotifier;
                var recordingExceptionNotifier = new RecordingExceptionNotifier();
                GlobalRegistry.UIExceptionNotifier = recordingExceptionNotifier;
                try
                {
                    DatabaseReverseEngineerer databaseReverseEngineerer = GetDatabaseReverseEngineer(schemaExtractor);
                    DBDatabase dbDatabase = databaseReverseEngineerer.CreateDatabaseInfo(connection.Database);


                    databaseReverseEngineerer.PopulateDBDatabase(dbDatabase);
            
                    IDBPostProcessor dbPostProcessor = GetPostProcessor(vendor);
                    dbPostProcessor.Process(dbDatabase);


                    //If there was no database then no need to merge databases.
                    if(ReverseEngineering.StoredDatabase == null)
                    {
                        new SolutionTraverser().MarkChangeTypeAdd(dbDatabase);
                        ReverseEngineering.StoredDatabase = dbDatabase;
                        recordingExceptionNotifier.RethrowRecordedException();
                        return;
                    }

                    MergeDatabases(dbDatabase);
                    recordingExceptionNotifier.RethrowRecordedException();
                }
                finally
                {
                    GlobalRegistry.UIExceptionNotifier = originalExceptionNotifier;
                }
            }
            finally
            {
                connection.Dispose();
            }
        }

        private void MergeDatabases(DBDatabase dbDatabase)
        {
            SetupMergeableBORegistry();
            var databaseMerger = MergeableBORegistry.Instance.Resolve<DBDatabase>();
            ReverseEngineering.StoredDatabase = (IDBDatabase) databaseMerger.MergeBOFrom(dbDatabase, ReverseEngineering.StoredDatabase);
            databaseMerger.UpdatePendingChangeType((DBDatabase)ReverseEngineering.StoredDatabase);
        }

        public static void SetupMergeableBORegistry()
        {
            MergeableBORegistry.Instance.Register<DBDatabase, DBMergeDBDatabase>();
            MergeableBORegistry.Instance.Register<DBTable, DBMergeDBTable>();
            MergeableBORegistry.Instance.Register<DBColumn, DBMergeDBColumn>();
            MergeableBORegistry.Instance.Register<DBPrimaryKey, DBMergeDBPrimaryKey>();
            MergeableBORegistry.Instance.Register<DBPrimaryKeyColumn, DBMergeDBPrimaryKeyColumn>();
            MergeableBORegistry.Instance.Register<DBKey, DBMergeDBKey>();
            MergeableBORegistry.Instance.Register<DBKeyColumn, DBMergeDBKeyColumn>();
            MergeableBORegistry.Instance.Register<DBForeignKey, DBMergeDBForeignKey>();
            MergeableBORegistry.Instance.Register<DBForeignKeyColumn, DBMergeDBForeignKeyColumn>();
        }

        private static IDBPostProcessor GetPostProcessor(string vendor)
        {
            switch (vendor.ToUpper())
            {
                case "MYSQL":
                    return new DBMySqlPostProcessor();
                default:
                    return new DBDefaultPostProcessor();
            }
        }

        protected DatabaseReverseEngineerer GetDatabaseReverseEngineer(IDBSchemaExtractor schemaExtractor)
        {
            var reverseEngineeringSettings = ReverseEngineering.ReverseEngineeringSettings;
            DatabaseReverseEngineerer databaseReverseEngineerer = new DatabaseReverseEngineerer(reverseEngineeringSettings, schemaExtractor);
            string vendorName = ReverseEngineering.DatabaseConnectionInfo.Vendor.ToUpper();
            IDBDataTypeMapping dataTypeMapping = GetDBDataTypeMapping(vendorName);
            databaseReverseEngineerer.DBTypeConverter = new DBTypeConverter(dataTypeMapping.GetDefaultTypeMappings());
            return databaseReverseEngineerer;
        }

        private IDbConnection CreateOpenConnection()
        {
            DatabaseConfig databaseConfig = ReverseEngineering.DatabaseConnectionInfo.CreateDatabaseConfig();
            IDatabaseConnection databaseConnection = databaseConfig.GetDatabaseConnection();
            IDbConnection connection = databaseConnection.GetConnection();
            if (connection.State == ConnectionState.Closed) connection.Open();
            return connection;
        }

        protected virtual IDBDataTypeMapping GetDBDataTypeMapping(string vendorName)
        {
            switch (vendorName.ToUpper())
            {
                case "MYSQL":
                    return new DBDataTypeMappingMySQL();
                case "SQLSERVER":
                    return new DBDataTypeMappingSQLServer();
                case "ACCESS":
                    return new DBDataTypeMappingAccess();
                default:
                    return null;
            }
        }

        protected virtual DBSchemaExtractor GetDBSchemaExtractor(string vendorName, IDbConnection dbConnection)
        {

            switch (vendorName.ToUpper())
            {
                case "MYSQL":
                    return new DBSchemaExtractorMySQL(dbConnection);
                case "SQLSERVER":
                    return new DBSchemaExtractorSQLServer(dbConnection);
                case "ACCESS":
                    return new DBSchemaExtractor_Access(dbConnection);
                default:
                    return null;
            }
        }
    }


}